﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using qtAiGamesLibrary;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace qtAiGamesLibrary
{
    public class SteeringBehaviours
    {
        MovingEntity _ParentEntity;

        public enum Deceleration
        {
            Slow = 3,
            Normal = 2,
            Fast = 1
        }
        
        #region * Private Members *
        
        // Wander Variables
        private float _WanderRadius;
        private float _WanderDistance;
        private float _WanderJitter;
        private Vector2 _vWanderTarget;

        #endregion

        #region * PUBLIC Properties
        /// <summary>
        /// Radius of wander Circle
        /// </summary>
        public float WanderRadius
        {
            get { return _WanderRadius; }
            set { _WanderRadius = value; }
        }

        /// <summary>
        /// The distance the wander circle is laid infront of the agent
        /// </summary>
        public float WanderDistance
        {
            get { return _WanderDistance; }
            set { _WanderDistance = value; }
        }

        /// <summary>
        /// The maximum amount of random displacement that can be added each second
        /// </summary>
        public float WanderJitter
        {
            get { return _WanderJitter; }
            set { _WanderJitter = value; }
        }
        #endregion

        

        public SteeringBehaviours(MovingEntity Parent)
        {
            Random rand = new Random();
            _ParentEntity = Parent;

            // Setup default Wander
            SetupWander(100.2f, 200.0f, 80.0f);
            
        }

        public void SetupWander( float fWanderRadius, float fWanderDistance, float fWanderJitter)
        {
            _WanderRadius = fWanderRadius;
            _WanderDistance = fWanderDistance;
            _WanderJitter = fWanderJitter;

            // Setup things for wander
            //stuff for the wander behavior
            float theta = (float)(Utils.RandPrecise() * (Math.PI * 2));

            //create a vector to a target position on the wander circle
            _vWanderTarget = new Vector2((float)(_WanderRadius * Math.Cos(theta)),
                                         (float)(_WanderRadius * Math.Sin(theta)));

            
        }


        /// <summary>
        /// The object moves towards the specified target!
        /// </summary>
        /// <param name="TargetPos"></param>
        /// <returns></returns>
        public Vector2 Seek(Vector2 TargetPos)
        {
            Vector2 DesiredVelocity = TargetPos - _ParentEntity.vPos;
            DesiredVelocity.Normalize();
            DesiredVelocity = DesiredVelocity * (float)_ParentEntity.MaxSpeed;

            return (DesiredVelocity - _ParentEntity.Velocity);
        }

        /// <summary>
        /// Flee from the targetpos
        /// </summary>
        /// <param name="TargetPos"></param>
        /// <returns></returns>
        public Vector2 Flee(Vector2 TargetPos)
        {
            const float PanicDistanceSq = 100.0f * 100.0f;

            if (Vector2.DistanceSquared(_ParentEntity.vPos, TargetPos) > PanicDistanceSq)
            {
                return Vector2.Zero;
            }

            Vector2 DesiredVelocity = _ParentEntity.vPos - TargetPos;
            DesiredVelocity.Normalize();
            DesiredVelocity = DesiredVelocity * (float)_ParentEntity.MaxSpeed;

            return (DesiredVelocity - _ParentEntity.Velocity);
        }

        

        /// <summary>
        /// Arrives at a position. The object comes to a stop gradually. Makes it look more real
        /// </summary>
        /// <param name="TargetPos"></param>
        /// <param name="deceleration"></param>
        /// <returns></returns>
        public Vector2 Arrive(Vector2 TargetPos, Deceleration deceleration)
        {
            Vector2 ToTarget = TargetPos - _ParentEntity.vPos;

            float dist = ToTarget.Length();

            if (dist > 0)
            {
                // To finetweak the deceleration
                const float DecelerationTweaker = 0.3f;

                // required speed for the desired deceleration 
                float speed = (float)dist / ((float)deceleration * DecelerationTweaker);

                // Make sure we clamp it to the max speed allowed
                speed = (float)Math.Min(speed, _ParentEntity.MaxSpeed);

                Vector2 DesiredVelocity = ToTarget * speed / dist;

                return (DesiredVelocity - _ParentEntity.Velocity);
            }

            return Vector2.Zero;
        }

        /// <summary>
        /// Guesses where the evader will be and tries to intercept heem
        /// </summary>
        /// <param name="Evader"></param>
        /// <returns></returns>
        public Vector2 Pursuit(MovingEntity Evader)
        {
            // If the evader is ahead and facing us, we just seek for its current positon
            Vector2 ToEvader = Evader.vPos - _ParentEntity.vPos;

            double RelativeHeading = Vector2.Dot(_ParentEntity.Heading, Evader.Heading);

            if ((Vector2.Dot(ToEvader, _ParentEntity.Heading) > 0) && (RelativeHeading < -0.95)) // 18 Degrees. We might have to include some Global define we can change for this!
            {
                return Seek(Evader.vPos);
            }

            // Ok, its not facing us!

            // Lets calculate where the evader will be a certain amount of time in the furture
            double LookAheadTime = ToEvader.Length() / (_ParentEntity.MaxSpeed + Evader.MaxSpeed);

            // Now all we need to do is seek to the predicted future position
            return Seek(Evader.vPos + Evader.Velocity * (float)LookAheadTime);
        }

        /// <summary>
        /// Evade a puruer, pretty much the same as Pursuit, except we flee!
        /// </summary>
        /// <param name="Pursuer"></param>
        /// <returns></returns>
        public Vector2 Evade(MovingEntity Pursuer)
        {
            // We don't care if we its facing use as we want to get away, no matter what way its facing :)
            Vector2 toPursuer = Pursuer.vPos - _ParentEntity.vPos;

            // Lets calculate where the puruer will be a certain amount of time in the furture
            double LookAheadTime = toPursuer.Length() / (_ParentEntity.MaxSpeed + Pursuer.MaxSpeed);

            // Now all we need to do is seek to the predicted future position
            return Flee(Pursuer.vPos + Pursuer.Velocity * (float)LookAheadTime);
        }

        /// <summary>
        /// The agent wanders on a path, this path uses Reynolds wander cicrle solution ;)
        /// </summary>
        /// <returns></returns>
        public Vector2 Wander()
        {
            // lets generate a small random vector and add it to the targets position
            // the target being where we are going, which we save for the next frames etc
            //create a vector to a target position on the wander circle
            Vector2 tmpVect = new Vector2((float)(Utils.RandClamped() * _WanderJitter),
                                         (float)(Utils.RandClamped() * _WanderJitter));

            _vWanderTarget += tmpVect;

            // Normalise it - Yes I spell it the English way - Yes, with English I mean UK english! :P
            _vWanderTarget.Normalize();

            // Increase the vector length to the radius of the circle
            _vWanderTarget *= _WanderRadius;

            // Now we reporject the target onto the circle!
            // And I am jsut going to reuse my temp vector variable for this. I am like that sorry!
            
            tmpVect = new Vector2(_WanderDistance, 0);
            tmpVect = _vWanderTarget + tmpVect;

            // tmpVect is the Target in local space currently
            // So we need to convert it to world space
            tmpVect = Utils.Transformations.PointToWorldSpace(tmpVect, _ParentEntity.Heading, _ParentEntity.Side, _ParentEntity.vPos);

            // and lets steer towards it :D
            return (tmpVect - _ParentEntity.vPos);

        }


        public void DrawDebug(GraphicsDevice GD, SpriteBatch spriteBatch)
        {
            // I want to render the Wandering info
            qtAiGamesLibrary.qtDebug.PrimitiveLine tmpPrimLine = new qtDebug.PrimitiveLine(GD);

            tmpPrimLine.CreateCircle(_WanderRadius, 100);

            //calculate the center of the wander circle
            Vector2 m_vTCC = Utils.Transformations.PointToWorldSpace(new Vector2(_WanderDistance * 1, 0),
                                                 _ParentEntity.Heading,
                                                 _ParentEntity.Side,
                                                 _ParentEntity.vPos);

            tmpPrimLine.Position = m_vTCC;

//             Vector2 tmpVect = new Vector2(_WanderDistance,0);
// 
//             tmpVect = Utils.Transformations.PointToWorldSpace((_vWanderTarget + tmpVect) * 1.0f,
//                                   _ParentEntity.Heading,
//                                   _ParentEntity.Side,
//                                   _ParentEntity.vPos);
// 
//             tmpPrimLine.Position = tmpVect;



            tmpPrimLine.Render(spriteBatch);

        }
    }
}
